//支持心跳检测的连接对象

package websocket

import (
	"github.com/gorilla/websocket"
	"net/http"
	"sync"
	"time"
)

// 连接对象
type Conn struct {
	idleMu sync.Mutex

	Uid string

	*websocket.Conn         //ws连接对象
	s               *Server //继承Server，可能会用到Server中的方法

	idle              time.Time     //连接空闲时间
	maxConnectionIdle time.Duration //最大空闲时间

	done chan struct{} //关闭的通道
}

// 创建连接对象
func NewConn(s *Server, w http.ResponseWriter, r *http.Request) *Conn {
	//得到ws连接对象c
	c, err := s.upgrade.Upgrade(w, r, nil)
	if err != nil {
		s.Errorf("[IN CONNECTION.GO] server handler ws upgrade err %v", err)
		return nil
	}

	//conn是支持心跳检测的ws连接对象
	conn := &Conn{
		Conn:              c,
		s:                 s,
		idle:              time.Now(),
		maxConnectionIdle: s.opt.maxConnectionIdle,
		done:              make(chan struct{}),
	}

	go conn.keepalive()

	return conn
}

// 在读时更新idle
func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
	messageType, p, err = c.Conn.ReadMessage()

	c.idleMu.Lock()
	defer c.idleMu.Unlock()

	c.idle = time.Time{} //重置空闲时间
	return
}

// 在写时更新idle
func (c *Conn) WriteMessage(messageType int, data []byte) error {
	c.idleMu.Lock()
	defer c.idleMu.Unlock()

	err := c.Conn.WriteMessage(messageType, data) //单独使用这个方法是线程不安全的
	c.idle = time.Now()                           //重置空闲时间
	return err
}

// 避免重复关闭同一个通道
func (c *Conn) Close() error {
	select {
	case <-c.done:
	default:
		close(c.done)
	}

	return c.Conn.Close()
}

func (c *Conn) keepalive() {
	//从 google.golang.org\grpc@v1.67.0\internal\transport\http2_server.go 中复制

	// Initialize the different timers to their default values.
	idleTimer := time.NewTimer(c.maxConnectionIdle)
	defer func() {
		// We need to drain the underlying channel in these timers after a call
		// to Stop(), only if we are interested in resetting them. Clearly we
		// are not interested in resetting them here.
		idleTimer.Stop()
	}()

	for {
		select {
		case <-idleTimer.C:
			c.idleMu.Lock()
			idle := c.idle
			if idle.IsZero() { // The connection is non-idle.
				c.idleMu.Unlock()
				idleTimer.Reset(c.maxConnectionIdle)
				continue
			}
			val := c.maxConnectionIdle - time.Since(idle)
			c.idleMu.Unlock()
			if val <= 0 {
				// The connection has been idle for a duration of keepalive.MaxConnectionIdle or more.
				// Gracefully close the connection.
				c.s.Close(c)
				return
			}
			idleTimer.Reset(val)
		case <-c.done:
			return
		}
	}
}
